#include "XMLParser.h"
#include "xmlexceptions.h"
#include <stack>

std::queue<ParseEle> & xml_parser::get_token_queue(){
	return this->m_elequeue;
}

void xml_parser::parse( const std::string & str ){

	int ret = parse_syntax(str);

	if (ret) {
		throw xml_syntax_exception();
	}

	//Validates the syntax-parsed XML file
	ret = validate();

	if (ret) {
		throw xml_validate_exception();
	}

}

int xml_parser::parse_syntax(const std::string & str){
	//XXX Todo

	ParseEle el; //An element to be copied to stack for each element obtained.
	int currPos; 
	bool error = false;
	std::string::const_iterator ch = str.begin();

	while (ch < str.end() && !error )
	{
        el.attrs.clear();
        el.txt = "";
		error = true;
		currPos = ch - str.begin();

		//Skip all whitespace.
		while (str[currPos] == ' ' ||
			  str[currPos] == '\n' ||
			  str[currPos] == '\t'){ currPos++; }

		//Check for an opening tag
		switch (str[currPos]) {
		case '<':
		{
			//Inside a tag, read in rest of tag
			//XXX Check for entity references
			//Check for things that shouldn't be in a tag
			int nextCntrChar = str.find_first_of(">\n<", currPos+1);

			//Makes sure we aren't at the end of the file yet.
			if (nextCntrChar == std::string::npos){
				//XXX Error - on syntax reached end of file first.
                throw xml_syntax_exception();
			}

			else if (str[currPos+1] == ' ' ||
				str[nextCntrChar] == '\n' ||
				str[nextCntrChar] == '<') {
                
                throw xml_syntax_exception();
				//XXX Error - leading whitespace
				//XXX Error - Reached newline inbetween a tag
				//XXX Error - tried to open a new tag while still in one.
			}
			//End of a tag
			else if (str[nextCntrChar] == '>') {
				error = 
					parse_tag(
					std::string(str.begin() + currPos,
						str.begin() + nextCntrChar+1),
					el);
			}
			else {
                throw xml_syntax_exception();
				//XXX Error - unknown
			}

			ch = str.begin() + nextCntrChar+1;
			break;
		}
		default:
		{
			//Text line
			el.pt = PARSE_TYPE::TEXT;
			
			int nextBrack = str.find_first_of("<>", currPos+1);
 
			if (nextBrack == str.npos) {
				nextBrack = str.size();
				//Error - Text without any more brackets
                //XXX For now, ignore
                error = false;
                ch = str.begin() + nextBrack;
                continue;
			}
			else if (str[nextBrack] == '>'){
				//XXX Error - Closing bracket without opening brack
			}
			else { 
				//could be < or end of string.  Validator will take care of it if end of string
				//because it won't see a CLOSE parsetype.
				el.txt = (str.substr(currPos, (nextBrack)-(currPos)));
				error = false;
			}

			ch = str.begin() + nextBrack;
		}
		} //Endswitch

		//No errors so push the element back.
		if (!error){
			m_elequeue.push(el);
           // printf("pushing ele on stack: %s\n", el.txt.c_str());
           // printf("Type: %d\n", el.pt == PARSE_TYPE::OPEN ? 0 : el.pt == PARSE_TYPE::CLOSE ? 1 : 2);
		}
	}//endfor

	return error;
}

int xml_parser::parse_tag( const std::string & tag,
	ParseEle & pel )
{

	if (tag[tag.size()-2] == '/'){
		pel.needClose = false;
	}
	else {
		pel.needClose = true;
	}

	int firstCharPos = 1;
	if (tag.size() == 0){
		//XXX Error - Empty tag
		return 1;
	}

	if (tag[firstCharPos] == ' ' || tag[firstCharPos] == '\n'){
		//XXX Error - Leading whitespace
		return 1;
	}

	//Check if open tag of element of closing tag of element.
	if (tag[firstCharPos] == '/'){ 
		if (tag.size() == 3) {
			//Error - <\> is invalid
			return 1;
		}
		else {
			pel.pt = PARSE_TYPE::CLOSE; 
			firstCharPos++;
		}
	}
	else { pel.pt = PARSE_TYPE::OPEN; }

    
    int nameendpos = tag.find_first_of(" >", firstCharPos);
    while (tag[nameendpos] == ' '){
        nameendpos++;
    }

			
	std::string txtstr = (tag.substr(firstCharPos, (nameendpos) - firstCharPos ) );

	rltw(txtstr);

	pel.txt = txtstr;
    
    parse_attr(tag.substr(nameendpos, tag.size() - nameendpos), pel);
    
    if (pel.attrs.size() > 0){
        //printf("After parsing, pel's attributes: \n");
        
        for (int i = 0; i < pel.attrs.size(); ++i){
            //printf(" %s , %s\n", pel.attrs[i].first.c_str(), pel.attrs[i].second.c_str());
        }
    }
	return 0;
}

//Take in tag without name (just attr="adf"> )
int xml_parser::parse_attr( const std::string & tag, ParseEle & pel ){
    
    int pos = 0;
    
    
    if (tag[pos] == '>'){
        return 0;
    }
    
    int wasattr = 0;
    
    while (true){
        wasattr = get_next_attr( tag, pos, pel );
        
        if (wasattr == 1){
            return 1;
        }
        
        if (wasattr == 2){
            break;
        }
    }
    
    return 0;
}

//Gets the attribute
int xml_parser::get_next_attr( const std::string & tag, int & pos, ParseEle & pel ) {
    int eqpos = tag.find_first_of("=", pos);
    
    if (eqpos == std::string::npos){
        //xxx error -> no closing >
        return 1;
    }
    
    //Add values between
    std::string attrnam = tag.substr(pos, (eqpos) - pos);
    
    //Add everything after
    int namepos = tag.find_first_not_of(" ", eqpos+1);
    
    if (tag[namepos] == '>') { 
        //XXX error -> no attribute name
        return 1; 
    }
    
    int endval = tag.find_first_of("\"\'>", namepos+1);
	if (tag[endval] == '>'){
		//XXX error -> reached end of tag
		return 1;
	}
 
    
    std::string attrval = tag.substr(namepos, (endval+1) - namepos);
    
	rltw(attrnam);
	rltw(attrval);

	std::pair<std::string, std::string> attrpair =
		std::make_pair(attrnam, attrval);

	//Checks the attributes to make sure well-formed.
	int error = check_attr(attrpair);

    pel.attrs.push_back(attrpair);

	//Remove quotes

	//printf("Attr error: %d\n", error);
    pos = endval +1;
    
    if (tag[endval] == '>'){
        return 2;
    }
    
    return 0;
    
}

int xml_parser::check_attr(std::pair<std::string, std::string> & inattr){
	if ( (inattr.second[0] == '\"' && inattr.second[inattr.second.size()-1] == '\"') ||
		 (inattr.second[0] == '\'' && inattr.second[inattr.second.size()-1] == '\'') )
	{
		inattr.second = inattr.second.substr(1, inattr.second.size()-2);
		return 0;
	}

	return 1;
}


void xml_parser::rltw( std::string & inout) {
	if (inout.size() == 0){
		return;
	}
	int spos = inout.size() -1;

	if (inout[spos] == ' '){
		while (inout[spos] == ' ' && spos > 0){ spos--; }
	}

	int fpos = 0;
	//find leading whitespace if any
	while (inout[fpos] == ' '){ fpos++; }

	inout = inout.substr(fpos, (spos+1) - fpos);

	return;
}

int xml_parser::validate() {

	//Check open and closing tags match.

	std::stack<ParseEle> openstack;

	std::queue<ParseEle> initqueue = m_elequeue;


	ParseEle * currEle;

	while (initqueue.size()){
		currEle = &initqueue.front();

		switch(currEle->pt) {
		case PARSE_TYPE::OPEN : 
		{
			if (currEle->needClose == true){
				openstack.push(*currEle);
			}
		}

		break;

		case PARSE_TYPE::CLOSE :
		{
            if (openstack.size() == 0 ){
                //printf("OPENSTACK SIZE: %d\n", openstack.size());
                throw xml_validate_exception();
            }
            if (openstack.top().txt != currEle->txt) {

                throw xml_validate_exception();
			//XXX Error - Clost without open tag

            //printf("openstack txt: %s\n", openstack.top().txt.c_str());
            //printf("Currele txt: %s\n", currEle->txt.c_str());
			}

			//XXX Create element from tag.
			openstack.pop();

		}

		break;

		default:

			//XXX Todo

			break;

		}
		initqueue.pop();
	}

	if (openstack.size() != 0){
		//XXX Error open tag without closing tag.
            throw xml_validate_exception();
		return 1;
	}

	return 0;

}